\bibliographystyle{gbt7714-2005}

\chapter{综合案例——使用CI和Bs快速开发网站}

\epigraphhead[60]{\epigraph{纸上得来终觉浅，绝知此事要躬行。}{\textit{节选自陆游《冬夜读书示子聿》}}}
在这一章中，我们将使用CodeIgnite框架和Bootstrap框架设计一个内容管理系统。

我们先从首页开始，首页主要由导航条、页脚部分、正文部分组成。

\section{准备框架文件}
\begin{enumerate}
	\item 我们先将CodeIgnite框架复制到项目文件夹，包括index.php和application及system三个文件及文件夹。\footnote{先暂时删除application目录中的.htaccess文件，以保证application目录中文件的访问权限。}
	\item 复制Bootstrap框架的必要文件夹（css、fonts、js）到application/views目录中。
	\item 下载jQuery（注意版本，Bootstrap框架使用的是1.11.1）并将文件复制到application/views/js目录中。
\end{enumerate}

\section{新建首页视图和控制器}
\begin{enumerate}
	\item 在controllers目录中新建home文件夹，并在其中创建home控制器：
\begin{lstlisting}
<?php if ( ! defined( 'BASEPATH' ) ) exit( 'No direct script access allowed' );
/**
 * 首页控制器
 */
class Home extends CI_Controller {
	public function index() {
		$this->load->view( 'home\index.html' );
	}
}
// PHP脚本结束
\end{lstlisting}
	\item \href{http://getbootstrap.com/getting-started/#template}{基于Bootstrap提供的模版}，\cite{Bootstrap，2014}新建空白首页，并将其保存到application/views/home目录中：
\begin{lstlisting}
<!DOCTYPE html>
<html lang="zh-cmn-Hans-CN">
<head>
	<meta charset="utf-8">
	<!-- 优先使用IE最新版本和 Chrome -->
	<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
	<!-- 360 浏览器内核控制 -->
	<meta name="renderer" content="webkit|ie-comp|ie-stand">
	<!-- 控制手机和平板的显示比例 -->
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<!-- SEO 三项优化 -->
	<!-- 页面描述 -->
	<meta name="description" content="" />
	<!-- 页面关键词 -->
	<meta name="keywords" content=""/>
	<!-- 页面标题 -->
	<title>首页</title>
	<!-- Bootstrap core CSS -->
	<link href="../css/bootstrap.css" rel="stylesheet">
	<!-- 自定义样式表 -->
	<link href="../css/home.css" rel="stylesheet">
	<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
	<!--[if lt IE 9]>
	<script src="http://cdn.bootcss.com/html5shiv/3.7.0/html5shiv.min.js"></script>
	<script src="http://cdn.bootcss.com/respond.js/1.3.0/respond.min.js"></script>
	<![endif]-->
</head>
<body>

	<script src="../js/jquery-1.11.1.js"></script>
	<script src="../js/bootstrap.js"></script>
</body>
</html>
\end{lstlisting}

尤其注意引入js脚本的顺序，先引入jquery，再引入bootstrap。
\end{enumerate}

\section{设置CodeIgnite默认控制器和自动装载项目}
\begin{enumerate}
	\item 打开application/config/routes.php文件，修改如下内容：\cite{EllislabInc，2014}
	\begin{lstlisting}
$route['default_controller'] = "home/home";
	\end{lstlisting}
	这样，我们就可以更方便地访问项目首页。
	\item 打开application/config/autoload.php文件，修改如下内容：\cite{EllislabInc，2014}
	\begin{lstlisting}
	$autoload['helper'] = array('url');
	\end{lstlisting}

	URL辅助函数在项目中需要经常使用，因此，我们在框架中设置为自动加载。
\end{enumerate}

\section{修改引入文件的路径}
上面空白文件中引入的外部样式表和JavaScript脚本文件，当使用CodeIgnite框架访问时会出现错误。这是因为，通过home控制器加载视图后，CodeIgnite框架的相对路径都是以index.php为参照对象，因此，需要修改外部样式表和JavaScript脚本文件的引用路径。

我们借助于CodeIgnite框架的URL辅助函数可以方便地实现路径的生成。如：

\begin{lstlisting}
<link href="<?php echo base_url('application/views/css/bootstrap.css'); ?>" rel="stylesheet">
\end{lstlisting}

按照上述方式，逐一对引入的外部文件路径进行修改。

\section{添加网站头部}
\begin{enumerate}
	\item 在index.html视图中，增加如下内容：
	\begin{lstlisting}
	<div class="masthead">
		<div class="container">
			<nav class="navbar">
        <!-- Brand and toggle get grouped for better mobile display -->
        <div class="navbar-header">
          <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#navbar-collapse-1">
            <span class="sr-only">Toggle navigation</span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
          </button>
          <a class="navbar-brand" href="#">首页</a>
        </div>

        <!-- Collect the nav links, forms, and other content for toggling -->
        <div class="collapse navbar-collapse" id="navbar-collapse-1">
          <ul class="nav navbar-nav">
            <li class="active"><a href="#">导航条菜单</a></li>
            <li><a href="#">导航条菜单</a></li>
            <li><a href="#">导航条菜单</a></li>
            <li><a href="#">导航条菜单</a></li>
            <li class="dropdown">
              <a href="#" class="dropdown-toggle" data-toggle="dropdown">下拉菜单 <b class="caret"></b></a>
              <ul class="dropdown-menu">
                <li><a href="#">Action</a></li>
                <li><a href="#">Another action</a></li>
                <li><a href="#">Something else here</a></li>
                <li class="divider"></li>
                <li><a href="#">Separated link</a></li>
              </ul>
            </li>
          </ul>
          <form class="navbar-form navbar-right" role="search">
		        <div class="form-group">
		          <input type="text" class="form-control" placeholder="Search">
		        </div>
		        <button type="submit" class="btn btn-default">搜索</button>
		      </form>
          <ul class="nav navbar-nav navbar-right">
            <li><a href="#">注册</a></li>
            <li><a href="#">登录</a></li>
          </ul>
        </div><!-- /.navbar-collapse -->
      </nav>
    </div><!-- /.container -->
   </div><!-- /.masthead -->
	\end{lstlisting}
	\item 在home.css中，增加如下代码：
	\begin{lstlisting}
.masthead {
  background-color: #393839;
  margin-bottom: 20px;
  -webkit-box-shadow: inset 0 -2px 5px rgba(0,0,0,.1);
          box-shadow: inset 0 -2px 5px rgba(0,0,0,.1);
}
.masthead a {
	color: #fff;
}
.masthead .navbar {
	margin: 20px 0;
}
.masthead .nav > li > a:hover, .masthead .nav > li > a:focus {
    background-color: #fff;
    color: #000;
    text-decoration: none;
}
	\end{lstlisting}
\end{enumerate}

\section{添加焦点区}
\begin{enumerate}
	\item 在index.html视图中，增加如下内容：
\begin{lstlisting}
  <div class="container">
  	<div class="jumbotron">
        <h1>听学联秘书长的"占中"辩解</h1>
        <p>This is a template for a simple marketing or informational website. It includes a large callout called a jumbotron and three supporting pieces of content. Use it as a starting point to create something more unique.</p>
        <p><a class="btn btn-primary btn-lg" role="button">详细阅读 &raquo;</a></p>
    </div>
   </div>
\end{lstlisting}
	\item 在main.css中，为焦点区设置样式：
	\begin{lstlisting}
	.jumbotron h1 {
    color: #9d1518;
    font-size: 46px;
    font-weight: bold;
    font-family: "微软雅黑";
}
	\end{lstlisting}
\end{enumerate}

\section{创建登录控制器}
接下来，我们创建负责用户登录、注册及退出的控制器。
\begin{enumerate}
	\item 在application/controllers中创建user目录，在该目录中存放有关用户的控制器；
	\item 在user目录中，建立user.php文件，文件内容如下：
	\begin{lstlisting}
class User extends CI_Controller {
  /**
   * 显示用户注册界面
   */
  public function login_form() {
    // 载入视图
    $this->load->view( 'user/login.html' );
  }
// php文件user.php到此结束
	\end{lstlisting}
	该控制器将调用位于application/views/user目录中的login.html文件。
	\item 在首页中添加登录入口的超级链接，找到如下内容：
	\begin{lstlisting}
		<li><a href="#">登录</a></li>
	\end{lstlisting}
	使用site\_url辅助函数，生成指向登录控制器的链接：
	\begin{lstlisting}
		<li><a href="<?php echo site_url('user/user/login_form'); ?>">登录</a></li>
	\end{lstlisting}
\end{enumerate}

\section{创建登录页面}
利用Bootstrap框架预设的面板、表单、栅格、按钮等内容，创建登录表单：
\begin{lstlisting}[label=login]
<div class="container">
		<div class="panel panel-default form-signin">
  		<div class="panel-heading">
    		<h3 class="panel-title">请输入登录信息</h3>
  		</div>
			<div class="panel-body">
				<form class="form-horizontal" role="form" method="POST" action="#">
					<div class="form-group">
						<label for="email" class="col-sm-2 control-label">Email</label>
						<div class="col-sm-8">
							<input type="email" class="form-control" name="uemail" placeholder="请输入你注册时的邮箱" required autofocus value=""></div>
						</div>
					<div class="form-group">
						<label for="password" class="col-sm-2 control-label">Password</label>
						<div class="col-sm-8">
							<input type="password" class="form-control" name="upassword" placeholder="请输入你的密码" required value=""></div>
					</div>
					<div class="form-group">
						<div class="col-sm-offset-2 col-sm-8	">
							<button class="btn btn-lg btn-primary btn-block" type="submit">登　录</button>
						</div>
					</div>
				</form>
			</div>
		</div>
	</div>
\end{lstlisting}
其中使用到Bootstrap的预设格式有：\cite{Bootstrap，2014a}
\begin{description}
	\item [.container] Bootstrap 需要为页面内容和栅格系统包裹一个 .container 容器。.container 类用于固定宽度并支持响应式布局的容器。
	\item [.panel] .panel 是Bootstrap设定的面板，设置了基本的边框和内边距以包含内容。
	\item [.panel-default] 用以设定面板的默认效果，还可以是.panel-warning等等。
	\item [.form-horizontal] 通过为表单添加 .form-horizontal 类，并联合使用 Bootstrap 预置的栅格类，可以将 label 标签和控件组水平并排布局。这样做将改变 .form-group 的行为，使其表现为栅格系统中的行（row），因此就无需再额外添加 .row 了。
	\item [.col-sm-2] 使用栅格系统，在小型屏幕上占据2行（总共12行）。
	\item [.col-sm-8] 使用栅格系统，在小型屏幕上占据8行（总共12行）。
	\item [.form-group] 将 label 元素和前面提到的控件包裹在 .form-group 中可以获得最好的排列。
	\item [.form-control] .form-control 类的 <input>、<textarea> 和 <select> 元素都将被默认设置宽度属性为 width: 100\%;。
	\item [.btn] 快速创建一个带有预定义样式的按钮。
	\item [.btn-primary] 设定按钮的预设样式。
	\item [.btn-lg] 改变按钮的大小。
	\item [.btn-block] 给按钮添加 .btn-block 类可以将其拉伸至父元素100\%的宽度，而且按钮也变为了块级（block）元素。
\end{description}

注意：在使用CodeIgnite表单验证类时，表单相关元素应该使用name属性，而不是id属性对元素进行命名。
\begin{lstlisting}[emph={name},emphstyle=\bfseries]
<input type="email" class="form-control" name="uemail" placeholder="请输入你注册时的邮箱" required autofocus value="">
\end{lstlisting}

\section{使用CodeIgnite表单验证}
当用户提交表单后，接受数据的脚本需要完成以下任务：
\begin{enumerate}
	\item 检查必须的数据。如用户名不能为空。
	\item 验证数据是否为正确的类型，合适的标准。例如用户名不能是已存在的他人用户名，或者甚至是一个保留字等。
	\item 清理数据使其安全。如将某些字符转义，防止执行SQL语句。
	\item 如果需要，预格式化数据（数据需要清除空白吗？经过 HTML 编码？等。）
	\item 准备数据，插入数据库。
\end{enumerate}

虽然这些任务都不是多困难，但仍然需要开发人员编写很多代码，而CodeIgnite框架提供了表单验证类，能对用户提交的数据进行验证，并给出错误提示，大大提高了开发效率。

首先，我们设定表单提交处理的链接入口，将表单代码\ref{login}中的：
\begin{lstlisting}
<form class="form-horizontal" role="form" method="POST" action="#">
\end{lstlisting}

修改为：
\begin{lstlisting}
<form class="form-horizontal" role="form" method="POST" action="<?php echo site_url('/user/user/login/') ?>">
\end{lstlisting}

也就是说，当用户提交表单后，我们使用user控制器中的login方法进行处理。

\subsection{表单验证的步骤}
在login方法中，使用CodeIgnite框架提供的表单验证类进行数据验证。表单验证的过程如下：
\begin{enumerate}
	\item 装载表单验证类form\_validation；
	\item 设置表单验证规则set\_rules；
	\item 运行表单验证；
	\item 验证通过后，进行下一步操作。
\end{enumerate}
\begin{lstlisting}
public function login() {
    // 载入验证类
    $this->load->library( 'form_validation' );
    // 设置验证规则
    // 验证规则的三个参数分别是字段名、提示信息、验证规则
    $this->form_validation->set_rules( 'uemail', '邮箱', 'required|valid_email' );
    // 运行验证规则
    if ( $this->form_validation->run() ) {
      //do something
    } else {
      $this->login_form();
    }
  }
\end{lstlisting}

\subsection{表单验证辅助函数}
当用户输入不符合要求时，应该给用户一个提示信息，并而不是用户在暗黑中摸索。CodeIgnite框架中有相关的辅助函数form\_error可以让开发人员设置错误提示信息。

在表单视图文件的相应位置，增加表单验证错误信息显示：
\begin{lstlisting}
<input type="email" class="form-control" name="uemail" placeholder="请输入你注册时的邮箱" required autofocus value=""><?php echo form_error('uemail'); ?>
\end{lstlisting}

除此之外，即便用户输入不符合验证规则，我们应该让用户在重新填写表单时保留之前已经填写的信息。重新填写可以使用CodeIgnite框架表单辅助函数set\_value、set\_select等。
\begin{lstlisting}
<input type="email" class="form-control" name="uemail" placeholder="请输入你注册时的邮箱" required autofocus value="<?php echo set_value('uemail'); ?>"><?php echo form_error('uemail'); ?>
\end{lstlisting}

\section{创建自定义验证规则}
CodeIgnite表单验证类内置了许多验证规则，可在\href{http://codeigniter.org.cn/user_guide/libraries/form_validation.html}{官方网站表单验证}页面内中查看全部内置规则。常用的有：
\begin{description}
	\item [required] 非空，必须要有值。
	\item [is\_unique] 如果表单元素的值与指定数据表栏位有重复，则返回False。用法is\_unique[table.field]
	\item [valid\_email] 如果表单元素值包含不合法的email地址，则返回FALSE。
\end{description}

这些内置验证规则虽很丰富，但在有的情况下，我们还需要自定义验证规则，在CodeIgnite中使用回调方法就能实现，如：希望密码必须包含大小写字母和数字，并且不能少于6位。

在user控制器的login方法中，增加如下代码：
\begin{lstlisting}
$this->form_validation->set_rules( 'upassword', '密码', 'callback_password_recheck' );
\end{lstlisting}

其中callback\_password\_recheck参数表示调用password\_recheck验证方法。该方法内容如下：
\begin{lstlisting}
public function password_recheck( $str ) {
    // 设置密码正则匹配模式
    $pattern = '/(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])\w{6,}/';
    // 执行正则验证
    if ( !preg_match( $pattern, $str ) ) {
      // 设置错误提示信息
      $this->form_validation->set_message( 'password_recheck', '%s 必须同时包含大写字母、小写字母和数字，并且6位以上' );
      return FALSE;
    } else {
      return TRUE;
    }
  }
\end{lstlisting}

其中，set\_message函数用来设置错误提示信息，当用户输入的密码不符合要求时，将看到此方法设定的提示信息。
正则表达式的内容，读者可参阅\ref{re4password}。

\section{接受表单数据}
当用户提交的表单信息全部都通过验证规则后，脚本的主要任务是从安全性方面对用户提交的数据进行处理，如防止SQL注入攻击、防止跨站脚本攻击\footnote{理论上，所有可输入的地方没有对输入数据进行处理的话，都会存在XSS漏洞，漏洞的危害取决于攻击代码的威力，攻击代码也不局限于script。其原理是攻击者向有XSS漏洞的网站中输入(传入)恶意的HTML代码，当其它用户浏览该网站时，这段HTML代码会自动执行，从而达到攻击的目的。如，盗取用户Cookie、破坏页面结构、重定向到其它网站等。}等等。CodeIgnite框架提供了输入类input用来完成这一任务。input类无需手动加载，CodeIgnite框架会自动加载此类。

输入类会自动执行以下任务：
\begin{enumerate}
	\item 过滤 GET/POST/COOKIE 数组键，只允许字母-数字(以及一些其它的)字符。
	\item 可以过滤跨站脚本攻击 (Cross-site Scripting Hacks) 此功能可全局打开（enabled globally），或者按要求打开。
	\item 换行符统一换为 \textbackslash{n}（Windows 下为 \textbackslash{r}\textbackslash{n}）
\end{enumerate}

自动过滤跨站脚本攻击的功能，在默认情况下是关闭的，故需修改配置文件（application/config目录中的config.php），修改如下内容：
\begin{lstlisting}
$config['global_xss_filtering'] = TRUE;
\end{lstlisting}

这样，我们就可以使用input类来接受用户提交的数据了：
\begin{lstlisting}
$data = array(
        'uemail'    => $this->input->post( 'uemail' ),
        // 使用md5方式加密密码
        'upassword' => md5( $this->input->post( 'upassword' ) ),
      );
\end{lstlisting}

Input类的常用方法有：
\begin{description}
	\item [post()] input类的post方法用来获取全局数组post中的数据，如果数据不存在，将返回布尔值FALSE，该方法的第二个参数为TRUE时，将会对数据进行XSS过滤；
	\item [get()] 获取全局数组get中的数据，其他同上。
	\item [cookie()] 获得全局数组cookie中的数据，其它同上。
	\item [server()] 获得全局数组server中的数据，其它同上。
\end{description}

\section{创建管理员数据库}
当脚本对用户提交的数据进行检验后，应该从数据库中读取数据，核对用户信息。

\subsection{创建数据库}
\begin{enumerate}
	\item 使用phpmyadmin工具，建立freepress数据库，编码方式选择uft8mb4\footnote{方便存储手机用户提交的信息}。
	\item 建立user表。
	\begin{lstlisting}
	CREATE TABLE IF NOT EXISTS `users` (
  `uid` int(11) NOT NULL AUTO_INCREMENT,
  `uemail` varchar(100) NOT NULL,
  `upassword` varchar(32) NOT NULL,
  `uregdate` int(10) NOT NULL,
  PRIMARY KEY (`uid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
	\end{lstlisting}
\end{enumerate}

\subsection{配置CodeIgnite数据库选项}
若要使用CodeIgnite提供的数据库相关功能，需要提供数据库配置信息，并在适当的地方加载数据库类。
\begin{enumerate}
	\item 修改CodeIgnite数据库配置文件：
	\begin{lstlisting}
$db['default']['hostname'] = 'localhost';
$db['default']['username'] = 'root';
$db['default']['password'] = '';
$db['default']['database'] = 'freepress';
$db['default']['dbdriver'] = 'mysqli';
$db['default']['dbprefix'] = '';
$db['default']['pconnect'] = TRUE;
$db['default']['db_debug'] = TRUE;
$db['default']['cache_on'] = FALSE;
$db['default']['cachedir'] = '';
$db['default']['char_set'] = 'utf8';
$db['default']['dbcollat'] = 'utf8mb4_general_ci';
	\end{lstlisting}
	\item 修改自动加载设置文件(application/config/autoload.php)，使之能自动加载数据库类：
\end{enumerate}

\begin{lstlisting}
$autoload['libraries'] = array('database');
\end{lstlisting}

\section{使用CodeIgnite操作数据库}
当我们设置好数据库信息，加载了数据库类之后，就可以使用数据库进行开发。

\subsection{使用Active Recode方法}
CodeIgniter 使用的是修改过的 Active Record 数据库模式。 这种模式是以较少的程序代码来实现信息在数据库中的获取，插入，更改。 有时只用一两行的代码就能完成对数据库的操作。

\subsubsection{插入数据}
\$this->db->insert();生成一条基于你所提供的数据的SQL插入字符串并执行查询。你可以向函数传递数组(或者对象），数组的键名必须和数据库的字段名一致。
\begin{lstlisting}
$this->db->insert( 'users' , $data );
\end{lstlisting}

\subsubsection{选择数据}
\$this->db->get();运行选择查询语句并且返回结果集。可以获取一个表的全部数据。

\subsubsection{链式操作}
\begin{lstlisting}
$this->db->where( $array )->get( 'users' )->result_array();
\end{lstlisting}
链式操作大大提高了开发的效率，也使得脚本更加容易阅读。

\subsection{创建用户模型}
在application/models目录中，创建user子目录，新建user\_model.php，内容如下：
\begin{lstlisting}
class User_model extends CI_model{
	/**
	 * [add 添加用户注册信息]
	 *
	 * @param [array] $data [用户注册信息，包括用户名、密码和注册时间]
	 */
	public function add( array $data ) {
		// 使用CI中的 Active Record 方法，快速操作数据库
		// 插入数组的参数有两个，第一个为数据表名，
		// 第二个为要插入的数据（是个数组，里面包含了要插入的字段名和对应的值）
		$this->db->insert( 'users' , $data );
	}
	/**
	 * [check 检查用户登录信息是否存在]
	 *
	 * @param [array] $data [用户名、密码]
	 * @return [array]       [以数组形式返回用户信息]
	 */
	public function check( array $data ) {
		$array = array( 'uemail' => $data['uemail'], 'upassword' => $data['upassword'] );
		return $this->db->where( $array )->get( 'users' )->result_array();
	}
}
\end{lstlisting}

\subsection{在控制器中加载模型}
在user控制器中，添加构造方法，以便在控制器中使用user\_model。

\begin{lstlisting}
  // 自动装载模型
  public function __construct() {
    parent::__construct();
    $this->load->model( 'user/user_model', 'user' );
  }
\end{lstlisting}

model方法的第一个参数为模型所在路径及名称，第二个参数可选，表示模型名称的简写，即用‘user’表示‘user\_model’。

\subsection{在控制器中调用模型的方法}
\begin{lstlisting}
// 调用模型对应的添加方法
$this->user->add( $data );
\end{lstlisting}

为方便调试，我们可在必要的时候开启CodeIgnite的调试模式：
\begin{lstlisting}
$this->output->enable_profiler(TRUE);
\end{lstlisting}

\section{使用CodeIgnite提供的session类}
\subsection{自动加载session类}
\begin{lstlisting}
$autoload['libraries'] = array('database', 'session');
\end{lstlisting}

\subsection{设置session密匙}
\begin{lstlisting}
$config['encryption_key'] = 'ersdfgfgfdigjdfg123';
\end{lstlisting}

\subsection{读写session}
\begin{lstlisting}
 $sessionData = array(
          'uemail' => $this->input->post( 'uemail' ),
        );
        // 将用户名写入session
        $this->session->set_userdata( $sessionData );
\end{lstlisting}

\section{加载自定义函数}
在CodeIgnite框架中，如何自定义及加载自定义函数，可参见\ref{helper}。将\ref{helper}中自定义函数内容保存到相应目录（application/helpers），并使其自动加载。
\begin{lstlisting}
$autoload['helper'] = array('url', 'tools');
\end{lstlisting}

\subsection{使用自定义函数调整到特定网页}
\begin{lstlisting}
 message( '/user/user/login_form', '登录信息不正确，请重新输入' );
\end{lstlisting}

\section{后台管理控制器及视图}
在之前的内容中，我们已经介绍了视图的创建、相关文件的引入、控制器的创立等内容，在此，不再赘述。下面，我们基于\href{http://v3.bootcss.com/examples/dashboard/}{Bootstrap框架提供的示例文件}快速实现后台管理功能。

\subsection{后台控制器admin}
\begin{lstlisting}
<?php if ( ! defined( 'BASEPATH' ) ) exit( 'No direct script access allowed' );
/**
 * 后台控制器
 */
class Admin extends CI_Controller {
	/**
	 * 后台首页显示方法
	 */
	public function index() {
		$this->load->view( 'admin\index.html' );
	}
}
// PHP脚本结束
\end{lstlisting}

\subsection{使用自定义函数实现跳转}
当用户登录成功后，使用自定义函数message，给出提示信息，并跳转到后台首页：
\begin{lstlisting}
 message( '/admin/admin/index/', '登录成功！' );
\end{lstlisting}

\subsection{后台视图文件}
下面我们设计管理员登录成功后，看到的后台首页视图。

在application/views/admin目录中，创建index.html。

\subsubsection{头部}
\begin{lstlisting}
<!DOCTYPE html>
<html lang="zh-cmn-Hans-CN">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="">
    <meta name="author" content="">
    <title>FreePress后台管理页面</title>
  <!-- Bootstrap core CSS -->
  <link href="<?php echo base_url('application/views/css/bootstrap.css'); ?>" rel="stylesheet">
  <!-- 自定义样式表 -->
  <link href="<?php echo base_url('application/views/css/dashboard.css'); ?>" rel="stylesheet">
    <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
    <!--[if lt IE 9]>
      <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
      <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
    <![endif]-->
  </head>
\end{lstlisting}

\subsubsection{顶部固定的导航条}
\begin{lstlisting}
<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
      <div class="container-fluid">
        <div class="navbar-header">
          <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
            <span class="sr-only">Toggle navigation</span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
          </button>
          <a class="navbar-brand" href="#">Project name</a>
        </div>
        <div id="navbar" class="navbar-collapse collapse">
          <ul class="nav navbar-nav navbar-right">
            <li><a href="#">Dashboard</a></li>
            <li><a href="#">Settings</a></li>
            <li><a href="#">Profile</a></li>
            <li><a href="#">Help</a></li>
          </ul>
          <form class="navbar-form navbar-right">
            <input type="text" class="form-control" placeholder="Search...">
          </form>
        </div>
      </div>
    </nav>
\end{lstlisting}

注意data-target和id的对应关系。

\subsubsection{左侧导航条}
\begin{lstlisting}
<div class="container-fluid">
      <div class="row">
        <div class="col-sm-3 col-md-2 sidebar">
          <ul class="nav nav-sidebar">
            <li class="active"><a href="#">Overview</a></li>
            <li><a href="#">Reports</a></li>
            <li><a href="#">Analytics</a></li>
            <li><a href="#">Export</a></li>
          </ul>
          <ul class="nav nav-sidebar">
            <li><a href="">Nav item</a></li>
            <li><a href="">Nav item again</a></li>
            <li><a href="">One more nav</a></li>
            <li><a href="">Another nav item</a></li>
            <li><a href="">More navigation</a></li>
          </ul>
          <ul class="nav nav-sidebar">
            <li><a href="">Nav item again</a></li>
            <li><a href="">One more nav</a></li>
            <li><a href="">Another nav item</a></li>
          </ul>
        </div>
\end{lstlisting}

“行（row）”必须包含在 .container （固定宽度）或 .container-fluid （100\% 宽度）中，以便为其赋予合适的排列（aligment）和内补（padding）。

其中sidebar和nav-sidebar属性都非Bootstrap框架固有属性，自定义的CSS代码如下：
\begin{lstlisting}
.sidebar {
  display: none;
}
@media (min-width: 768px) {
  .sidebar {
    position: fixed;
    top: 51px;
    bottom: 0;
    left: 0;
    z-index: 1000;
    display: block;
    padding: 20px;
    overflow-x: hidden;
    overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */
    background-color: #f5f5f5;
    border-right: 1px solid #eee;
  }
}

/* Sidebar navigation */
.nav-sidebar {
  margin-right: -21px; /* 20px padding + 1px border */
  margin-bottom: 20px;
  margin-left: -20px;
}
.nav-sidebar > li > a {
  padding-right: 20px;
  padding-left: 20px;
}
.nav-sidebar > .active > a,
.nav-sidebar > .active > a:hover,
.nav-sidebar > .active > a:focus {
  color: #fff;
  background-color: #428bca;
}
\end{lstlisting}

其中当前选中的菜单为active样式。

\subsubsection{后台主体页面}
\begin{lstlisting}
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
          <h1 class="page-header">Dashboard</h1>
          <div class="row placeholders">
            <div class="col-xs-6 col-sm-3 placeholder">
              <img data-src="holder.js/200x200/auto/sky" class="img-responsive" alt="Generic placeholder thumbnail">
              <h4>Label</h4>
              <span class="text-muted">Something else</span>
            </div>
          </div>
          <h2 class="sub-header">Section title</h2>
          <div class="table-responsive">
            <table class="table table-striped">
              <thead>
                <tr>
                  <th>#</th>
                  <th>Header</th>
                  <th>Header</th>
                  <th>Header</th>
                  <th>Header</th>
                </tr>
              </thead>
              <tbody>
                <tr>
                  <td>1,001</td>
                  <td>Lorem</td>
                  <td>ipsum</td>
                  <td>dolor</td>
                  <td>sit</td>
                </tr>
              </tbody>
            </table>
          </div>
        </div>
\end{lstlisting}

这一部分内容，应该从index.html中独立出来，对应左侧菜单栏的任务功能。这样我们就把后台视图分解为三个部分：
\begin{enumerate}
	\item 顶端、侧边导航条
	\item 主体部分
	\item 页脚部分
\end{enumerate}

\section{后台首页视图及控制器}
在这一节中，我们完善后台首页的功能，实现对整站信息的概览显示。

\subsection{在admin控制器中实现多个视图文件的载入}
为了实现代码的重用，我们将上面的index.html文件分解为三个部分：head-nav.html、overview.html和foot.html。其中overview.html对应admin/index方法，而head-nav.html和foot.html文件内容则是公用部分。

现在，admin控制器的index方法修改为：
\begin{lstlisting}
	public function index() {
		// $this->load->view( 'admin\index.html' );
		// 为提高代码的重用，我们将index.html中的公用部分提取出来
		$this->load->view('admin\head-nav.html');
		$this->load->view('admin\overview.html');
		$this->load->view('admin\foot.html');
	}
\end{lstlisting}

\subsection{站点概览信息的视图显示}
我们将之前的overview.html内容，修改如下：
\begin{lstlisting}
        <div class="panel panel-default">
          <div class="panel-heading">站点环境</div>
          <div class="panel-body">
            <table class="table table-striped">
              <thead>
                <tr>
                  <th width="30%">项目</th>
                  <th>值</th>
                </tr>
              </thead>
              <tbody>
                <tr>
                  <td>主机名称</td>
                  <td><?php echo $this->input->server('SERVER_NAME'); ?></td>
                </tr>
                <tr>
                  <td>主机地址</td>
                  <td><?php echo $this->input->server('SERVER_ADDR'); ?></td>
                </tr>
                <tr>
                  <td>主机环境</td>
                  <td><?php echo $this->input->server('SERVER_SOFTWARE'); ?></td>
                </tr>
                <tr>
                  <td>CodeIgnite版本</td>
                  <td><?php echo CI_VERSION; ?></td>
                </tr>
                <tr>
                  <td>MySQL版本</td>
                  <td><?php echo mysql_get_server_info(); ?></td>
                </tr>
              </tbody>
            </table>
          </div><!-- end of .panel-body -->
        </div><!-- end of .panel -->
\end{lstlisting}

在这个文件中，我们用到了CodeIgnite框架提供的input类，其中input类的server方法，实际上就是PHP的全局数组\$\_SERVER。

\section{为后台功能添加登录检验}
截至目前，我们虽然是从首页的登录页面进入的后台，但实际上，我们可以直接访问后台首页，从而绕过登录页面，这显然需要改进。

在admin的构造方法中，通过检验用户的session信息，就可判断用户是否登录。
\begin{lstlisting}
	public function __construct() {
		// 继承父类构造方法
    parent::__construct();
    // 判断用户是否登录
    if(!$this->session->userdata['uemail']) {
    	// 否则跳转到用户登录页面
    	redirect('user/login_form');
    }
  }
\end{lstlisting}

\section{首页导航条的增删改查}
接下来，我们通过对导航条的项目进行增删改查来练习数据库的操作。

\subsection{建立目录数据库}
\begin{lstlisting}
CREATE TABLE IF NOT EXISTS `category` (
  `cid` int(2) NOT NULL AUTO_INCREMENT,
  `cname` varchar(20) NOT NULL,
  `cparent` int(2) NOT NULL,
  PRIMARY KEY (`cid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=1 ;
\end{lstlisting}

\subsection{建立侧边栏入口及目录控制器}
使用site\_url函数建立入口：
\begin{lstlisting}
<li><a href="<?php echo site_url('admin/category/index') ?>">目录</a></li>
\end{lstlisting}

建立目录控制器：
\begin{lstlisting}
class Category extends CI_Controller {
	/**
	 * 构造方法，用于判断是否登录，自动加载必要资源。
	 */
	public function __construct() {
		// 继承父类构造方法
    parent::__construct();
    // 判断用户是否登录
    if(!$this->session->userdata['uemail']) {
    	// 否则跳转到用户登录页面
    	redirect('user/user/login_form');
    }
  }
	/**
	 * 调用视图方法
	 */
	public function index() {
		$this->load->view('admin\head-nav.html');
		$this->load->view('admin\category.html');
		$this->load->view('admin\foot.html');
	}
}
\end{lstlisting}

\subsection{}
\bibliography{../../bib/yangjh}
